package classes;


public class Matrix {
		public static final class Arrays {
			public static final String mString(float M[][]) {
				int	row, col;
				String	output = "";
				
				for(row=0; row<M.length; row++) {
					String	line = "";
					for(col=0; col<M.length; col++) {
						String	F = String.format(" [%3.2f]", M[row][col]);
						line += F;
					}
					output += line += "\n";
				}
				return output;
			}
					
			public static final float[][]	zeroMatrix() {
				return new float[][] {
						{	0f, 0f, 0f, 0f	},
						{	0f, 0f, 0f, 0f	},
						{	0f, 0f, 0f, 0f	},
						{	0f, 0f, 0f, 0f	}
					};
			}
			public static final float[][]	identity() {
				return new float[][] {
					{	1f, 0f, 0f, 0f	},
					{	0f, 1f, 0f, 0f	},
					{	0f, 0f, 1f, 0f	},
					{	0f, 0f, 0f, 1f	}
				};
			}
			public static final float[][]	translation(float dx, float dy, float dz) {
				return new float[][] {
					{	1f, 0f, 0f, dx	},
					{	0f, 1f, 0f, dy	},
					{	0f, 0f, 1f, dz	},
					{	0f, 0f, 0f, 1f	}
				};
			}
			public static final float[][]	scaling(float sx, float sy, float sz) {
				return new float[][] {
					{	sx, 0f, 0f, 0f	},
					{	0f, sy, 0f, 0f	},
					{	0f, 0f, sz, 0f	},
					{	0f, 0f, 0f, 1f	}
				};
			}
			public static final float[][]	rotationX(float radians) {
				return new float[][] {
					{	1f, 0f, 						0f, 						0f	},
					{	0f, (float) Math.cos(radians),	(float) -Math.sin(radians), 0f	},
					{	0f, (float) Math.sin(radians),	(float) Math.cos(radians),	0f	},
					{	0f, 0f, 						0f, 						1f	}
				};
			}
			public static final float[][]	rotationY(float radians) {
				return new float[][] {
					{	(float) Math.cos(radians),	0f,	(float) Math.sin(radians),	0f	},
					{	0f,							1f,	0f,							0f	},
					{	(float) -Math.sin(radians),	0f,	(float) Math.cos(radians),	0f	},
					{	0f,							0f, 0f, 						1f	}
				};
			}
			public static final float[][]	rotationZ(float radians) {
				return new float[][] {
					{	(float) Math.cos(radians),	(float) -Math.sin(radians),	0f,	0f	},
					{	(float) Math.sin(radians),	(float) Math.cos(radians),	0f,	0f	},
					{	0f,							0f,							1f,	0f	},
					{	0f,							0f,							0f, 1f	}
				};
			}
			public static final float[][]	perspectiveProjection(float near, float far) {
				return new float[][] {
					{	1f,	0f,	0f,					0f		},
					{	0f,	1f,	0f,					0f		},
					{	0f,	0f,	(near+far)/near,	0-far	},
					{	0f,	0f,	1/near,				0f		}
				};
			}

			public static final float[][]	multiply(float	M1[][], float M2[][]) {
				int	row, col, i;
				float	sum = 0;
				float	M3[][] = identity();
				for(row=0; row<4; row++) {
					for(col=0; col<4; col++) {
						sum = 0f;
						for(i=0; i<4; i++) {
							sum += M1[row][i] * M2[i][col];
						}
						M3[row][col] = sum;	// Dot product of M1.row(row) and M2.col(col)
					}
				}
				return M3;
			}
			
			public static final float[]	transform(float M[][], float V1[]) {
				//
				// This is the key vector that transforms the vector into the appropriate space
				//
				
				float	V2[] = new float[4];
				int	row, i;
				float	sum = 0;
				for(row=0; row<4; row++) {
					sum = 0f;
					for(i=0; i<4; i++) {
						float left, right;
						left = M[row][i];
						right = V1[i];
						sum += left * right;
					}
					V2[row] = sum;	// Dot product of M1.row(row) and M2.col(col)
				}
				return V2;
			}
		};
	
		public float	M[][];
		public float	get(int col, int row)				{	return M[col][row];		}
		public void		set(int col, int row, float value)	{	M[col][row] = value;	}
		public Vector	getCol(int col) {
			return new Vector(
				new float[] {
					M[0][col],
					M[1][col],
					M[2][col],
					M[3][col]
				}
			);
		}
		public Vector	getRow(int row) {
			return new Vector(
				new float[] {
					M[row][0],
					M[row][1],
					M[row][2],
					M[row][3]
				}
			);
		}

		public Matrix(float V[][]) {
			M = V;
		}

		public final	Matrix	multiply(Matrix M)	{	return new Matrix(Arrays.multiply(this.M, M.M));	}
		public final	Vector	transform(Vector V)	{	return new Vector(Arrays.transform(this.M, V.V));	}
		public final 	Matrix	inverse()			{	return new Matrix(Algebra.inverse(this.M));			} 
		public final	Matrix	clone()				{	return new Matrix(Algebra.copy(this.M));			}
		
		public static final Matrix	identity()	{	return new Matrix(Arrays.identity());			}
		public static final Matrix	blank()		{	return new Matrix(Arrays.zeroMatrix());			}
		
		public static final Matrix	scaling(float sx, float sy, float sz)			{	return new Matrix(Arrays.scaling(sx,sy,sx));					}
		public static final Matrix	translation(float dx, float dy, float dz)		{	return new Matrix(Arrays.translation(dx,dy,dx));				}
		public static final Matrix	rotationX(float radians)						{	return new Matrix(Arrays.rotationX(radians));					}
		public static final Matrix	rotationY(float radians)						{	return new Matrix(Arrays.rotationY(radians));					}
		public static final Matrix	rotationZ(float radians)						{	return new Matrix(Arrays.rotationZ(radians));					}
		
		public static final Matrix	rotationXY(float radiansX, float radiansY)		{	return rotationX(radiansX).multiply(rotationY(radiansY));		}
		public static final Matrix	rotationXZ(float radiansX, float radiansZ)		{	return rotationX(radiansX).multiply(rotationZ(radiansZ));		}
		public static final Matrix	rotationYZ(float radiansY, float radiansZ)		{	return rotationY(radiansY).multiply(rotationZ(radiansZ));		}
		
		public static final Matrix	perspectiveProjection(float near, float far)	{	return new Matrix(Arrays.perspectiveProjection(near, far));		}

		public final float determinant() throws Exception	{	return Algebra.determinant(M);		}	// Should never throw anything. Matrix is always square
		public final String niceString()					{	return Algebra.matrixString(M);		}
		
		public boolean equals(Matrix M2) {
			if (M2==null)	return false;
			return Algebra.isEqual(this.M, M2.M);
		}
	}
	